#include "ginnel.h"
#include "block.h"

#define LEVEL		3
#define WIDTH		320
#define HEIGHT		240
#define WALL_WIDTH	32
#define BAT_Y		(HEIGHT-32)
#define BAT_HEIGHT	4
#define BAT_WIDTH	48
#define BALL_WIDTH	5
#define FPS			(30*LEVEL)
#define BLOCKS_W	10
#define BLOCKS_H	8
#define BALL_SPEED	1
#define BAT_SPEED	(12-(3*LEVEL))

#define RIGHT(rect)		(rect->x+rect->w)
#define LEFT(rect)		(rect->x)
#define TOP(rect)		(rect->y)
#define BOTTOM(rect)	(rect->y+rect->h)

void render(SDL_Surface* screen, struct State* state);
void updateState(struct State* state);
SDL_Rect* rect(int x, int y, int w, int h);
Uint32 rgb(Uint8 r, Uint8 g, Uint8 b);
void cleanup(struct State* state);
struct State* initState();
void destroy(struct State* state, Block* block);

Uint32 wallCol;
SDL_Rect* backgroundRect;
SDL_Rect* leftWallRect;
SDL_Rect* rightWallRect;
SDL_Rect* topWallRect;
SDL_Rect* batRect;
FPSmanager* fpsManager;

SDL_PixelFormat* pixelFormat;

SDL_Rect* ballRect;

int main(int argc, char* argv[]) {
	SDL_Init(SDL_INIT_EVERYTHING);
	atexit(SDL_Quit);

	SDL_Surface* screen = SDL_SetVideoMode(WIDTH, HEIGHT, 32, SDL_HWSURFACE|SDL_DOUBLEBUF);
	pixelFormat = screen->format;

	wallCol = rgb(73, 70, 3);
	backgroundRect = rect(0,0,320,239);
	leftWallRect = rect(0, 0, WALL_WIDTH, HEIGHT);
	rightWallRect = rect(WIDTH-WALL_WIDTH, 0, WIDTH, HEIGHT);
	topWallRect = rect(0, 0, WIDTH, WALL_WIDTH);

	batRect = rect(0,BAT_Y,BAT_WIDTH,BAT_HEIGHT);
	ballRect = rect(0, 0, BALL_WIDTH, BALL_WIDTH);

	fpsManager = (FPSmanager*)malloc(sizeof(FPSmanager));
	SDL_initFramerate(fpsManager);
	SDL_setFramerate(fpsManager, FPS);

	struct State* state = initState();

	while(1) {
		updateState(state);
		if(!state->gameRunning) {
			break;
		}
		render(screen, state);
		SDL_framerateDelay(fpsManager);
	}
	cleanup(state);

	return 0;
}

void render(SDL_Surface* screen, struct State* state) {
	// draw background
	SDL_FillRect(screen, backgroundRect, rgb(0,0,0));

	// draw wall
	SDL_FillRect(screen, leftWallRect, wallCol);
	SDL_FillRect(screen, rightWallRect, wallCol);
	SDL_FillRect(screen, topWallRect, wallCol);

	// draw bat
	roundedRectangleColor(screen, batRect->x, batRect->y, batRect->x+batRect->w, batRect->y+batRect->h, 1, convertToRGBA(rgb(192,192,192)));

	// draw ball

	SDL_FillRect(screen, ballRect, rgb(255,255,255));

	// draw blocks
	int n;
	for(n = state->blocksNumber ; n-- ; ) {
		Block* block = state->blocksData[n];
		block->draw(screen, block);

//		SDL_FillRect(screen, block->rectBottom, rgb(255,0,255));
//		SDL_FillRect(screen, block->rectTop, rgb(255,0,255));
//		SDL_FillRect(screen, block->rectLeft, rgb(255,0,255));
//		SDL_FillRect(screen, block->rectRight, rgb(255,0,255));
	}

	// make updates appear on-screen
	SDL_Flip(screen);
}

void updateState(struct State* state) {
	SDL_Event event;
	while(SDL_PollEvent(&event)) {
		switch(event.type) {
		case SDL_KEYDOWN:
			if(event.key.keysym.sym == SDLK_LEFT) {
				state->batVel -= BAT_SPEED;
			} else if(event.key.keysym.sym == SDLK_RIGHT) {
				state->batVel += BAT_SPEED;
			}
			break;
		case SDL_KEYUP:
			if(event.key.keysym.sym == SDLK_LEFT) {
				state->batVel += BAT_SPEED;
			} else if(event.key.keysym.sym == SDLK_RIGHT) {
				state->batVel -= BAT_SPEED;
			} else if(event.key.keysym.sym == SDLK_SPACE && !state->ballInFlight) {
				state->ballVY -= 1;
				state->ballVX += (state->batX < (WIDTH/2) ? -1 : 1);
				state->ballY -= 1;
				state->ballInFlight = TRUE;
			} else if(event.key.keysym.sym == SDLK_ESCAPE) {
				state->gameRunning = FALSE;
			}
			break;
		case SDL_QUIT:
			state->gameRunning = FALSE;
			break;
		}
	}

	// bat movement
	int resolvedBatX = state->batX + state->batVel;
	if(resolvedBatX > leftWallRect->w && (resolvedBatX+BAT_WIDTH) < rightWallRect->x) {
		state->batX = resolvedBatX;
		if(!state->ballInFlight) {
			state->ballX = state->batX + (BAT_WIDTH/2);
		}
	}

	// bounce off walls
	if(hasCollided(ballRect, leftWallRect) || hasCollided(ballRect, rightWallRect)) {
		state->ballVX *= -1;
	} else if(hasCollided(ballRect, topWallRect)) {
		state->ballVY *= -1;
	}

	// bounce off blocks
	for(int n = state->blocksNumber ; n-- ; ) {
		if(!state->blocksData[n]->destroyed && hasCollided(ballRect, state->blocksData[n]->rectTop) && state->ballVY > 0) {
			state->ballVY = -1;
			destroy(state, state->blocksData[n]);
			if(hasCollided(ballRect, state->blocksData[n]->rectLeft) && state->ballVX > 0) {
				state->ballVX = -1;
			} else if(hasCollided(ballRect, state->blocksData[n]->rectRight) && state->ballVX < 0) {
				state->ballVX = 1;
			}
			break;
		} else if(!state->blocksData[n]->destroyed && hasCollided(ballRect, state->blocksData[n]->rectBottom) && state->ballVY < 0) {
			state->ballVY = 1;
			destroy(state, state->blocksData[n]);
			if(hasCollided(ballRect, state->blocksData[n]->rectLeft) && state->ballVX > 0) {
				state->ballVX = -1;
			} else if(hasCollided(ballRect, state->blocksData[n]->rectRight) && state->ballVX < 0) {
				state->ballVX = 1;
			}
			break;
		} else if(!state->blocksData[n]->destroyed && hasCollided(ballRect, state->blocksData[n]->rectLeft) && state->ballVX > 0) {
			state->ballVX = -1;
			destroy(state, state->blocksData[n]);
			if(hasCollided(ballRect, state->blocksData[n]->rectTop) && state->ballVY > 0) {
				state->ballVY = -1;
			} else if(hasCollided(ballRect, state->blocksData[n]->rectBottom) && state->ballVY < 0) {
				state->ballVY = 1;
			}
			break;
		} else if(!state->blocksData[n]->destroyed && hasCollided(ballRect, state->blocksData[n]->rectRight) && state->ballVX < 0) {
			state->ballVX = 1;
			destroy(state, state->blocksData[n]);
			if(hasCollided(ballRect, state->blocksData[n]->rectTop) && state->ballVY > 0) {
				state->ballVY = -1;
			} else if(hasCollided(ballRect, state->blocksData[n]->rectBottom) && state->ballVY < 0) {
				state->ballVY = 1;
			}
			break;
		}
	}

	// bounce back or DIE
	if((state->ballInFlight && hasCollided(ballRect, batRect))) {
		state->ballVY = -1;
		if(state->ballX < state->batX + BAT_WIDTH/6) {
			state->ballVX = -1;
		} else if(state->ballX > state->batX + (BAT_WIDTH/6)*5) {
			state->ballVX = 1;
		}
		state->ballY += (state->ballVY*state->ballSpeed);
	}

	if(state->ballY > (BAT_Y+4*BAT_HEIGHT)) {
		state->ballVX=0;
		state->ballVY=0;//TODO die
		state->ballInFlight = FALSE;
		state->ballY = BAT_Y-BALL_WIDTH;
		state->ballX = (WIDTH/2);
	}

	// apply ball movement
	state->ballX += (state->ballVX*state->ballSpeed);
	state->ballY += (state->ballVY*state->ballSpeed);

	ballRect->x = state->ballX;
	ballRect->y = state->ballY;
	batRect->x = state->batX;
}

void destroy(struct State* state, Block* block) {
	block->destroyed = TRUE;
	//TODO: give points
}

Uint32 rgb(Uint8 r, Uint8 g, Uint8 b) {
	return SDL_MapRGB(pixelFormat, r, g, b);
}

struct State* initState() {
	struct State* state = malloc(sizeof(struct State));
	memset(state, 0, sizeof(struct State));

	// set initial bat position
	state->batX = (WIDTH/2)-(BAT_WIDTH/2);
	state->ballY = BAT_Y-BALL_WIDTH;
	state->ballX = (WIDTH/2);
	state->ballInFlight = FALSE;
	state->gameRunning = TRUE;
	state->ballSpeed = BALL_SPEED;

	// create blocks
	state->blocksNumber = BLOCKS_H * BLOCKS_W;
	state->blocksData = malloc(sizeof(Block*)*state->blocksNumber);

	const int X_OFFSET = WIDTH / 2;
	const int Y_OFFSET = topWallRect->h + (topWallRect->h/8)*5;
	int x, y, counter = 0;
	for(x = (-1)*(BLOCKS_W/2) ; x < (BLOCKS_W/2) ; x++) {
		for(y = BLOCKS_H ; y-- ; ) {
			state->blocksData[counter++] = createBlock(x*BLOCK_WIDTH + X_OFFSET + (x*2), y*BLOCK_HEIGHT + Y_OFFSET + (y*2),
					y < 3 ? rgb(0,153,0) : (y < 6 ? rgb(153,0,0) : rgb(0,0,153)));
		}
	}

	return state;
}

void cleanup(struct State* state) {
	free(fpsManager);
	free(state);
	free(backgroundRect);
	free(topWallRect);
	free(leftWallRect);
	free(rightWallRect);
	free(batRect);
	free(ballRect);
}

